"""

Building and world design commands

"""
from django.conf import settings
from src.objects.models import ObjectDB, ObjAttribute
from src.players.models import PlayerAttribute
from src.utils import create, utils#, debug
from src.commands.default.muxcommand import MuxCommand
from src.commands.cmdhandler import get_and_merge_cmdsets
from src.commands.default.building import ObjManipCommand

# limit symbol import for API
__all__ = ("ObjManipCommand", "CmdSetObjAlias", "CmdCopy",
           "CmdCpAttr", "CmdMvAttr", "CmdCreate", "CmdDebug",
           "CmdDesc", "CmdDestroy", "CmdDig", "CmdTunnel", "CmdLink",
           "CmdUnLink", "CmdHome", "CmdListCmdSets", "CmdName",
           "CmdOpen", "CmdSetAttribute", "CmdTypeclass", "CmdWipe",
           "CmdLock", "CmdExamine", "CmdFind", "CmdTeleport",
           "CmdScript")

# used by @find
#CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS



class MyCmdSetObjAlias(MuxCommand):
    """
    Adding permanent aliases

    Usage:
      @alias <obj> [= [alias[,alias,alias,...]]]

    Assigns aliases to an object so it can be referenced by more
    than one name. Assign empty to remove all aliases from object.
    Observe that this is not the same thing as aliases
    created with the 'alias' command! Aliases set with @alias are
    changing the object in question, making those aliases usable
    by everyone.
    """

    key  = "@alias"
    aliases = "@setobjalias"
    locks = "cmd:perm(setobjalias) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Set the aliases."

        caller = self.caller
        if not self.lhs:
            string = "Usage: @alias <obj> [= [alias[,alias ...]]]"
            self.caller.msg(string)
            return
        objname = self.lhs

        # Find the object to receive aliases
        obj = caller.search(objname, global_search=True)
        if not obj:
            return
        if self.rhs == None:
            # no =, so we just list aliases on object.
            aliases = obj.aliases
            if aliases:
                caller.msg("Alias per '%s': %s" % (obj.key, ", ".join(aliases)))
            else:
                caller.msg("Non esistono alias per '%s'." % obj.key)
            return

        if not obj.access(caller, 'edit'):
            caller.msg("Non hai il permesso per farlo.")
            return

        if not self.rhs:
            # we have given an empty =, so delete aliases
            old_aliases = obj.aliases
            if old_aliases:
                caller.msg("Alias eliminati da %s: %s" % (obj.key, ", ".join(old_aliases)))
                del obj.dbobj.aliases
            else:
                caller.msg("Nessun alias da eliminare.")
            return

        # merge the old and new aliases (if any)
        old_aliases = obj.aliases
        new_aliases = [alias.strip().lower() for alias in self.rhs.split(',') if alias.strip()]
        # make the aliases only appear once
        old_aliases.extend(new_aliases)
        aliases = list(set(old_aliases))

	#the following line corrects a bug by f0nt4
	if obj.dbobj.aliases:
		del obj.dbobj.aliases


	# save back to object.
        obj.aliases = aliases
        # we treat this as a re-caching (relevant for exits to re-build their exit commands with the correct aliases)
        caller.msg("Alias per '%s': %s." % (obj.key, ", ".join(obj.aliases)))

class MyCmdCreate(ObjManipCommand):
    """
    @create - create new objects

    Usage:
      @create[/drop] objname[;alias;alias...][:typeclass], objname...
      @create[/key] objname

    switch:
       drop - automatically drop the new object into your current location (this is not echoed)
              this also sets the new object's home to the current location rather than to you.
       key  - create a key object for an lock object.

    Creates one or more new objects. If typeclass is given, the object
    is created as a child of this typeclass. The typeclass script is
    assumed to be located under game/gamesrc/types and any further
    directory structure is given in Python notation. So if you have a
    correct typeclass object defined in
    game/gamesrc/types/examples/red_button.py, you could create a new
    object of this type like this:

       @create button;red : examples.red_button.RedButton

    """

    key = "@create"
    locks = "cmd:perm(create) or perm(Builders)"
    help_category = "Building"

    def func(self):
        """
        Creates the object.
        """

        caller = self.caller

        if not self.args:
            string = "Usage: @create[/switch] <newname>[;alias;alias...] [:typeclass_path]"
            caller.msg(string)
            return

        if 'key' in self.switches:
            objname = self.lhs
            obj = caller.search(objname)
            if not obj:
                return
            if not hasattr(obj, "is_locked"):
                caller.msg("Non si puo' chiudere quindi non ha bisogno di una chiave.")
                return
            if not obj.db.keycode:
                caller.msg("Non puoi creare una chiave per l'oggetto se l'attributo keycode e' zero.")
                return
            lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id)
            typeclass = "game.gamesrc.objects.lock.Key"
            name = "la chiave di %s" % obj.name


            name = obj.name
            if hasattr(obj, "doorname"):
                name = obj.doorname
            keyname = "la chiave di %s" % name

            key = create.create_object(typeclass, keyname, caller,
                                       home=obj.location, aliases=['chiave'], locks=lockstring, report_to=caller)
            key.db.keycode = obj.db.keycode
            key.db.r_desc = "Una chiave e' qui per terra."
            caller.msg("Hai creato una nuova chiave per l'oggetto %s." % name)
            return
            

        # create the objects
        for objdef in self.lhs_objs:
            string = ""
            name = objdef['name']
            aliases = objdef['aliases']
            typeclass = objdef['option']

            # create object (if not a valid typeclass, the default
            # object typeclass will automatically be used)
            lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id)
            obj = create.create_object(typeclass, name, caller,
                                       home=caller, aliases=aliases, locks=lockstring, report_to=caller)
            if not obj:
                continue
            if aliases:
                string = "Hai creato un nuovo oggetto %s: %s (aliases: %s)."
                string = string % (obj.typeclass.typename, obj.name, ", ".join(aliases))
            else:
                string = "Hai creato un nuovo oggetto %s: %s."
                string = string % (obj.typeclass.typename, obj.name)
            if 'drop' in self.switches:
                if caller.location:
                    obj.home = caller.location
                    obj.move_to(caller.location, quiet=True)
        if string:
           caller.msg(string)

class MyCmdDesc(MuxCommand):
    """
    @desc - describe an object or room

    Usage:
      @desc[/r] [<obj> =] >description>
  
    switch:
       r  - set the rdesc attribute. This attribute contains
            the description of the object in a room.

    Setts the "desc" attribute on an
    object. If an object is not given,
    describe the current room.
    """
    key = "@desc"
    aliases = "@describe"
    locks = "cmd:perm(desc) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Define command"

        caller = self.caller
        if not self.args:
            caller.msg("Usage: @desc[/r] [<obj> =] >description>")
            return

        if self.rhs:
            # We have an =
            obj = caller.search(self.lhs)
            if not obj:
                return
            desc = self.rhs
        else:
            obj = caller.location
            desc = self.args
        # storing the description
        if 'r' in self.switches:
            obj.r_desc = desc
        else:
            obj.db.desc = desc
        caller.msg("Descrizione salvata per %s." % obj.key)

class MyCmdDig(ObjManipCommand):
    """
    @dig - build and connect new rooms to the current one

    Usage:
      @dig[/switches] roomname[;alias;alias...][:typeclass]
            [= exit_to_there[;alias][:typeclass]]
               [, exit_to_here[;alias][:typeclass]]

    Switches:
       tel or teleport - move yourself to the new room

    Examples:
       @dig kitchen = north;n, south;s
       @dig house:myrooms.MyHouseTypeclass
       @dig sheer cliff;cliff;sheer = climb up, climb down

    This command is a convenient way to build rooms quickly; it creates the new room and you can optionally
    set up exits back and forth between your current room and the new one. You can add as many aliases as you
    like to the name of the room and the exits in question; an example would be 'north;no;n'.
    """
    key = "@dig"
    locks = "cmd:perm(dig) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Do the digging. Inherits variables from ObjManipCommand.parse()"

        caller = self.caller

        if not self.lhs:
            string = "Usage: @dig[/teleport] roomname[;alias;alias...][:parent] [= exit_there"
            string += "[;alias;alias..][:parent]] "
            string += "[, exit_back_here[;alias;alias..][:parent]]"
            caller.msg(string)
            return

        room = self.lhs_objs[0]

        if not room["name"]:
            caller.msg("You must supply a new room name.")
            return
        location = caller.location

        # Create the new room
        typeclass = room['option']
        if not typeclass:
            typeclass = settings.BASE_ROOM_TYPECLASS

        # create room
        lockstring = "control:id(%s) or perm(Immortals); delete:id(%s) or perm(Wizards); edit:id(%s) or perm(Wizards)"
        lockstring = lockstring % (caller.dbref, caller.dbref, caller.dbref)

        new_room = create.create_object(typeclass, room["name"],
                                        aliases=room["aliases"], report_to=caller)
        new_room.locks.add(lockstring)
        alias_string = ""
        if new_room.aliases:
            alias_string = " (%s)" % ", ".join(new_room.aliases)
        room_string = "Created room %s(%s)%s of type %s." % (new_room, new_room.dbref, alias_string, typeclass)


        # create exit to room

        exit_to_string = ""
        exit_back_string = ""

        if self.rhs_objs:
            to_exit = self.rhs_objs[0]
            if not to_exit["name"]:
                exit_to_string = \
                    "\nNo exit created to new room."
            elif not location:
                exit_to_string = \
                  "\nYou cannot create an exit from a None-location."
            else:
                # Build the exit to the new room from the current one
                typeclass = to_exit["option"]
                if not typeclass:
                    typeclass = settings.BASE_EXIT_TYPECLASS

                new_to_exit = create.create_object(typeclass, to_exit["name"], location,
                                                   aliases=to_exit["aliases"],
                                                   locks=lockstring, destination=new_room, report_to=caller)
                alias_string = ""
                if new_to_exit.aliases:
                    alias_string = " (%s)" % ", ".join(new_to_exit.aliases)
                exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s."
                exit_to_string = exit_to_string % (location.name, new_room.name, new_to_exit,
                                                   new_to_exit.dbref, alias_string)

        # Create exit back from new room

        if len(self.rhs_objs) > 1:
            # Building the exit back to the current room
            back_exit = self.rhs_objs[1]
            if not back_exit["name"]:
                exit_back_string = \
                    "\nNo back exit created."
            elif not location:
                exit_back_string = \
                   "\nYou cannot create an exit back to a None-location."
            else:
                typeclass = back_exit["option"]
                if not typeclass:
                    typeclass = settings.BASE_EXIT_TYPECLASS

                new_back_exit = create.create_object(typeclass, back_exit["name"],
                                                     new_room, aliases=back_exit["aliases"],
                                                     locks=lockstring, destination=location, report_to=caller)
                alias_string = ""
                if new_back_exit.aliases:
                    alias_string = " (%s)" % ", ".join(new_back_exit.aliases)
                exit_back_string = "\nCreated Exit back from %s to %s: %s(%s)%s."
                exit_back_string = exit_back_string % (new_room.name, location.name,
                                                       new_back_exit, new_back_exit.dbref, alias_string)
        caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string))
        if new_room and ('teleport' in self.switches or "tel" in self.switches):
            caller.move_to(new_room)



class MyCmdTunnel(MuxCommand):
    """
    dig in often-used directions

    Usage:
      @tunnel[/switch] <direction> [= roomname[;alias;alias;...][:typeclass]]

    Switches:
      oneway - do not create an exit back to the current location
      tel - teleport to the newly created room

    Example:
      @tunnel n
      @tunnel n = house;mike's place;green building

    This is a simple way to build using pre-defined directions:
     {wn,ne,e,se,s,sw,w,nw{n (north, northeast etc)
     {wu,d{n (up and down)
     {wi,o{n (in and out)
    The full names (north, in, southwest, etc) will always be put as
    main name for the exit, using the abbreviation as an alias (so an
    exit will always be able to be used with both "north" as well as
    "n" for example). Opposite directions will automatically be
    created back from the new room unless the /oneway switch is given.
    For more flexibility and power in creating rooms, use @dig.
    """

    key = "@tunnel"
    aliases = ["@tun"]
    locks = "cmd: perm(tunnel) or perm(Builders)"
    help_category = "Building"

    # store the direction, full name, its opposite and aliases
    directions = {"n" : ("North", "s", "nord"),
                  "e" : ("East", "w", "est"),
                  "s" : ("South", "n", "sud"),
                  "w" : ("West", "e", "ovest"),
                  "u" : ("Up", "d", "su"),
                  "d" : ("Down", "u", "giu"),
                  "i" : ("In", "o", "fuori"),
                  "o" : ("Out", "i", "dentro")}

    def func(self):
        "Implements the tunnel command"

        if not self.args or not self.lhs:
            string = "Usage: @tunnel[/switch] <direction> [= roomname[;alias;alias;...][:typeclass]]"
            self.caller.msg(string)
            return
        if self.lhs not in self.directions:
            string = "@tunnel can only understand the following directions: %s." % ",".join(sorted(self.directions.keys()))
            string += "\n(use @dig for more freedom)"
            self.caller.msg(string)
            return
        # retrieve all input and parse it
        exitshort = self.lhs
        exitname, backshort, exitaliases = self.directions[exitshort]
        exitaliases = [exitaliases, exitshort]
        backname = self.directions[backshort][0]
        backaliases =  [self.directions[backshort][2], backshort]

        roomname = "Some place"
        if self.rhs:
            roomname = self.rhs # this may include aliases; that's fine.

        telswitch = ""
        if "tel" in self.switches:
            telswitch = "/teleport"
        backstring = ""
        if not "oneway" in self.switches:
            backstring = ", %s;%s" % (backname, ";".join(backaliases))
            self.caller.msg(backstring)
        #exitname = "%s - %s" % (exitname, roomname)
        # build the string we will use to call @dig
        digstring = "@dig%s %s = %s;%s%s" % (telswitch, roomname, exitname, ";".join(exitaliases), backstring)
        self.caller.msg(digstring)
        self.caller.execute_cmd(digstring)




class MyCmdName(ObjManipCommand):
    """
    cname - change the name and/or aliases of an object


    Usage:
      @name obj[/switch] = name;alias1;alias2
    
    Switches:
      door - set the name of a door


    Rename an object to something new.

    """

    key = "@name"
    aliases = ["@rename"]
    locks = "cmd:perm(rename) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "change the name"

        caller = self.caller
        if not self.args:
            string = "Usage: @name <obj> = <newname>[;alias;alias;...]"
            caller.msg(string)
            return

        if self.lhs_objs:
            objname = self.lhs_objs[0]['name']
            obj = caller.search(objname)
            if not obj:
                return
        if self.rhs_objs:
            newname = self.rhs_objs[0]['name']
            aliases = self.rhs_objs[0]['aliases']
        else:
            newname = self.rhs
            aliases = None
        if not newname and not aliases:
            caller.msg("Nessun nome o alias definito!")
            return


        astring = ""    
        # change the name and set aliases:
        if newname:
            if "door" in self.switches:
                # change the name of the door
                if not hasattr(obj.db, "is_door") and not obj.db.is_door:
                    caller.msg("Non e' una porta, non ha l'attributo door_name.")
                    return
                # change name of the linked door
                other_door = caller.search(obj.db.linkeddoor, global_search=True)
                obj.db.door_name = newname
                other_door.db.door_name = newname
                if aliases:
                    old_aliases = []
	            if obj.dbobj.aliases:
                        old_aliases = obj.dbobj.aliases
		        del obj.dbobj.aliases
                    obj.dbobj.aliases = list(set(old_aliases + aliases))
                    old_aliases = []
	            if other_door.dbobj.aliases:
                        old_aliases = other_door.dbobj.aliases
		        del other_door.dbobj.aliases
                    other_door.dbobj.aliases = list(set(old_aliases + aliases))
                    astring = " (%s)" % (", ".join(aliases))
                caller.msg("Nome dell'oggetto cambiato in '%s'%s." % (newname, astring))
                return
            obj.name = newname
        
        if aliases:
	    #the following line corrects a bug by f0nt4
	    if obj.dbobj.aliases:
		del obj.dbobj.aliases	

            obj.aliases = aliases
            astring = " (%s)" % (", ".join(aliases))
        # fix for exits - we need their exit-command to change name too
        if obj.destination:
            obj.flush_from_cache()
        caller.msg("Nome dell'oggetto cambiato in '%s'%s." % (newname, astring))


class MyCmdOpen(ObjManipCommand):
    """
    @open - create new exit

    Usage:
      @open <new exit>[;alias;alias..][:typeclass] [,<return exit>[;alias;..][:typeclass]]] = <destination>

    Handles the creation of exits. If a destination is given, the exit
    will point there. The <return exit> argument sets up an exit at the
    destination leading back to the current room. Destination name
    can be given both as a #dbref and a name, if that name is globally
    unique.

    """
    key = "@open"
    locks = "cmd:perm(open) or perm(Builders)"
    help_category = "Building"
    # store the direction, full name, its opposite and aliases
    directions = {"n" : ("North", "s", "nord"),
                  "e" : ("East", "w", "est"),
                  "s" : ("South", "n", "sud"),
                  "w" : ("West", "e", "ovest"),
                  "u" : ("Up", "d", "su"),
                  "d" : ("Down", "u", "giu"),
                  "i" : ("In", "o", "fuori"),
                  "o" : ("Out", "i", "dentro")}


    # a custom member method to chug out exits and do checks
    def create_exit(self, exit_name, location, destination, exit_aliases=None, typeclass=None):
        """
        Helper function to avoid code duplication.
        At this point we know destination is a valid location

        """
        caller = self.caller
        string = ""
        # check if this exit object already exists at the location.
        # we need to ignore errors (so no automatic feedback)since we
        # have to know the result of the search to decide what to do.

        exit_obj = caller.search(exit_name, ignore_errors=True, location=location)
        if len(exit_obj) > 1:
            # give error message and return
            caller.search(exit_name, ignore_errors=True, location=location)
            return
        if exit_obj:
            exit_obj = exit_obj[0]
            if not exit_obj.destination:
                # we are trying to link a non-exit
                string = "'%s' esiste gia' e non e' un uscita!\nSe vuoi convertirlo"
                string += "in un uscita devi assegnare la corretta destinazione prima."
                caller.msg(string % exit_name)
                return None
            # we are re-linking an old exit.
            old_destination = exit_obj.destination
            if old_destination:
                string = "L'uscita %s e' gia' esistente." % exit_name
                if old_destination.id != destination.id:
                    # reroute the old exit.
                    exit_obj.destination = destination
                    exit_obj.aliases = exit_aliases
                    string += " Cambiata la sua destinatione da '%s' a '%s' e aggiornati gli alias." % \
                        (old_destination.name, destination.name)
                else:
                    string += " Esiste gia' un uscita corretta."

        else:
            # exit does not exist before. Create a new one.
            if not typeclass:
                typeclass = settings.BASE_EXIT_TYPECLASS
            lockstring="control:id(%s) or perm(Immortals); delete:id(%s) or perm(Wizards); edit:id(%s) or perm(Wizards)"
            lockstring = lockstring % (caller.id, caller.id, caller.id)
            exit_obj = create.create_object(typeclass, key=exit_name,
                                            location=location,
                                            aliases=exit_aliases, locks=lockstring, report_to=caller)
            if exit_obj:
                # storing a destination is what makes it an exit!
                exit_obj.destination = destination
                string = "Creata una nuova uscita '%s' da %s a %s (alias: %s)." % (exit_name,location.name,
                                                                                 destination.name,
                                                                                 ", ".join([str(e) for e in exit_aliases]))
            else:
                string = "Error: Exit '%s' not created." % (exit_name)
        # emit results
        caller.msg(string)
        return exit_obj

    def func(self):
        """
        This is where the processing starts.
        Uses the ObjManipCommand.parser() for pre-processing
        as well as the self.create_exit() method.
        """
        caller = self.caller

        if not self.args or not self.rhs:
            string = "Utilizzo: @open[/switch] <direzione>[:typeclass]"
            string += "= <destinazione>"
            caller.msg(string)
            return

	dir_name = self.lhs_objs[0]['name']

            
        if dir_name not in self.directions:
            string = "@open puo' solo usare le seguenti direzioni: %s." % ",".join(sorted(self.directions.keys()))
            caller.msg(string)
            return

        # We must have a location to open an exit
        location = caller.location
        if not location:
            caller.msg("Non puoi creare un uscita da un non-luogo.")
            return

        dest_name = self.rhs
        # first, check so the destination exists.
        destination = caller.search(dest_name, global_search=True)
        if not destination:
            return

        # obtain needed info from cmdline
        exit_name = self.directions[dir_name][0]
        exit_aliases = [self.directions[dir_name][2], dir_name]
        exit_typeclass = self.lhs_objs[0]['option']

        # Create exit
        ok = self.create_exit(exit_name, location, destination, exit_aliases, exit_typeclass)
        if not ok:
            # an error; the exit was not created, so we quit.
            return
        ok.db.wild_loc = caller.db.wild_loc
        doorname = None
        if hasattr(ok.db, "door_name"):
            doorname = ok.db.door_name


        # Create back exit, if any
       #if len(self.lhs_objs) > 1:
        if not "oneway" in self.switches:
            back_dir = self.directions[dir_name][1]
            back_exit_name = self.directions[back_dir][0]
            back_exit_aliases = [self.directions[back_dir][2], back_dir]
            back_exit_typeclass = exit_typeclass
            ok2 =self.create_exit(back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass)
            if ok2:
                # sets the destination (x,y) in a wilderness
                ok2.db.wild_dest = caller.db.wild_loc
                # makes a link with the other side of the door
                if doorname:
                    ok2.db.door_name = doorname
                    ok.db.linkeddoor = ok2.id
                    ok2.db.linkeddoor = ok.id



class MyCmdSetAttribute(ObjManipCommand):
    """
    @set - set attributes

    Usage:
      @set <obj>/<attr> = <value>
      @set <obj>/<attr> =
      @set <obj>/<attr>

    Sets attributes on objects. The second form clears
    a previously set attribute while the last form
    inspects the current value of the attribute
    (if any).

    The most common data to save with this command are strings and
    numbers. You can however also set Python primities such as lists,
    dictionaries and tuples on objects (this might be important for
    the functionality of certain custom objects).  This is indicated
    by you starting your value with one of {c'{n, {c"{n, {c({n, {c[{n  or {c{ {n.
    Note that you should leave a space after starting a dictionary ('{ ')
    so as to not confuse the dictionary start with a colour code like \{g.
    Remember that if you use Python primitives like this, you must
    write proper Python syntax too - notably you must include quotes
    around your strings or you will get an error.

    """

    key = "@set"
    locks = "cmd:perm(set) or perm(Builders)"
    help_category = "Building"

    def convert_from_string(self, strobj):
        """
        Converts a single object in *string form* to its equivalent python
        type. Handles floats, ints, and limited nested lists and dicts
        (can't handle lists in a dict, for example, this is mainly due to
        the complexity of parsing this rather than any technical difficulty -
        if there is a need for @set-ing such complex structures on the
        command line we might consider adding it).

        We need to convert like this since all data being sent over the
        telnet connection by the Player is text - but we will want to
        store it as the "real" python type so we can do convenient
        comparisons later (e.g.  obj.db.value = 2, if value is stored as a
        string this will always fail).
        """

        def rec_convert(obj):
            """
            Helper function of recursive conversion calls.
            """
            # simple types
            try: return int(obj)
            except ValueError: pass
            try: return float(obj)
            except ValueError: pass
            # iterables
            if obj.startswith('[') and obj.endswith(']'):
                "A list. Traverse recursively."
                return [rec_convert(val) for val in obj[1:-1].split(',')]
            if obj.startswith('(') and obj.endswith(')'):
                "A tuple. Traverse recursively."
                return tuple([rec_convert(val) for val in obj[1:-1].split(',')])
            if obj.startswith('{') and obj.endswith('}') and ':' in obj:
                "A dict. Traverse recursively."
                return dict([(rec_convert(pair.split(":",1)[0]), rec_convert(pair.split(":",1)[1]))
                             for pair in obj[1:-1].split(',') if ":" in pair])
            # if nothing matches, return as-is
            return obj


        try:
            # under python 2.6, literal_eval can do this for us.
            from ast import literal_eval
            return literal_eval(strobj)
        except ImportError:
            # fall back to old recursive solution (don't support nested lists/dicts)
            return rec_convert(strobj.strip())
        except ValueError:
            pass
        return strobj

    def func(self):
        "Implement the set attribute - a limited form of @py."

        caller = self.caller
        if not self.args:
            caller.msg("Usage: @set obj/attr = value. Use empty value to clear.")
            return

        # get values prepared by the parser
        value = self.rhs
        objname = self.lhs_objattr[0]['name']
        attrs = self.lhs_objattr[0]['attrs']

        obj = caller.search(objname)
        if not obj:
            return

        string = ""
        if not value:
            if self.rhs == None:
                # no = means we inspect the attribute(s)
                if not attrs:
                    attrs = [attr.key for attr in obj.get_all_attributes()]
                for attr in attrs:
                    if obj.has_attribute(attr):
                        string += "\nAttributo %s/%s = %s" % (obj.name, attr, obj.get_attribute(attr))
                    else:
                        string += "\n%s non ha l'attributo '%s'." % (obj.name.capitalize(), attr)
                    # we view it without parsing markup.
                self.caller.msg(string.strip(), data={"raw":True})
                return
            else:
                # deleting the attribute(s)
                for attr in attrs:
                    if obj.has_attribute(attr):
                        val = obj.get_attribute(attr)
                        obj.del_attribute(attr)
                        string += "\Attributo cancellato '%s' (= %s) from %s." % (attr, val, obj.name)
                    else:
                        string += "\n%s non ha l'attributo '%s'." % (obj.name.capitalize(), attr)
        else:
            # setting attribute(s). Make sure to convert to real Python type before saving.
             for attr in attrs:
                try:
                    obj.set_attribute(attr, self.convert_from_string(value))
                    string += "\nAttributo creato %s/%s = %s" % (obj.name, attr, value)
                except SyntaxError:
                    if value.strip() and not value.strip()[0] in ("'", '"', "(", "{ ", "["):
                        obj.set_attribute(attr, value)
                        string += "\nAttributo creato %s/%s = %s" % (obj.name, attr, value)
                    else:
                        # this means literal_eval tried to parse a faulty string
                        string = "{RErrore nella sintassi Python del valore inserito. Assegnando un valore che inizia con"
                        string += "\nun carattere tra {r'{R, {r\"{R, {r[{R, {r({R o {r{{R e' inteso che tu stia inserendo una struttura Python"
                        string += "\napproriata come una list o un dictionary. Devi assicurarti quindi che la sintassi sia"
                        string += "\ncorretta. Ricorda anche che per inserire valori numerici come stringhe devi racchiuderli"
                        string += "\ntra virgolette (\"\"), altrimenti il valore verra' interpretato come integer o float.{n"
        # send feedback
        caller.msg(string.strip('\n'))

class MyCmdLock(ObjManipCommand):
    """
    lock - assign a lock definition to an object

    Usage:
      @lock <object>[ = <lockstring>]
      or
      @lock[/switch] object/<access_type>

    Switch:
      del - delete given access type
      view - view lock associated with given access type (default)

    If no lockstring is given, shows all locks on
    object.

    Lockstring is on the form
       'access_type:[NOT] func1(args)[ AND|OR][ NOT] func2(args) ...]
    Where func1, func2 ... valid lockfuncs with or without arguments.
    Separator expressions need not be capitalized.

    For example:
       'get: id(25) or perm(Wizards)'
    The 'get' access_type is checked by the get command and will
    an object locked with this string will only be possible to
    pick up by Wizards or by object with id 25.

    You can add several access_types after oneanother by separating
    them by ';', i.e:
       'get:id(25);delete:perm(Builders)'
    """
    key = "@lock"
    aliases = ["@locks"]
    locks = "cmd: perm(@locks) or perm(Builders)"
    help_category = "Building"

    def func(self):
        "Sets up the command"

        caller = self.caller
        if not self.args:
            string = "@lock <object>[ = <lockstring>] or @lock[/switch] object/<access_type>"
            caller.msg(string)
            return
        if '/' in self.lhs:
            # call on the form @lock obj/access_type
            objname, access_type = [p.strip() for p in self.lhs.split('/', 1)]
            obj = caller.search(objname)
            if not obj:
                return
            lockdef = obj.locks.get(access_type)
            if lockdef:
                string = lockdef[2]
                if 'del' in self.switches:
                    if not obj.access(caller, 'control'):
                        caller.msg("Non hai il permesso di farlo.")
                        return
                    obj.locks.delete(access_type)
                    string = "Lock cancellato: %s" % string
            else:
                string = "%s non ha lock per l'accesso a '%s'." % (obj, access_type)
            caller.msg(string)
            return

        if self.rhs:
            # we have a = separator, so we are assigning a new lock
            objname, lockdef = self.lhs, self.rhs
            obj = caller.search(objname)
            if not obj:
                return
            if not obj.access(caller, 'control'):
                caller.msg("Non hai il permesso di farlo.")
                return
            ok = obj.locks.add(lockdef, caller)
            if ok:
                caller.msg("Aggiunto il lock '%s' a %s." % (lockdef, obj))
            return

        # if we get here, we are just viewing all locks
        obj = caller.search(self.lhs)
        if not obj:
            return
        caller.msg(obj.locks)

class MyCmdExamine(ObjManipCommand):
    """
    examine - detailed info on objects

    Usage:
      examine [<object>[/attrname]]
      examine [*<player>[/attrname]]

    Switch:
      player - examine a Player (same as adding *)
      raw - don't parse escape codes for data.

    The examine command shows detailed game info about an
    object and optionally a specific attribute on it.
    If object is not specified, the current location is examined.

    Append a * before the search string to examine a player.

    """
    key = "@examine"
    aliases = ["@ex","ex", "exam", "examine"]
    locks = "cmd:perm(examine) or perm(Builders)"
    help_category = "Building"
    arg_regex = r"\s.*?|$"

    player_mode = False

    def format_attributes(self, obj, attrname=None, crop=True, raw=False):
        """
        Helper function that returns info about attributes and/or
        non-persistent data stored on object
        """

        headers = {"persistent":"\n{wPersistent attributes{n:",
                   "nonpersistent":"\n{wNon-persistent attributes{n:"}
        headers_noansi = {"persistent":"\nPersistent attributes:",
                          "nonpersistent":"\nNon-persistent attributes:"}
        if raw:
            headers = headers_noansi

        if attrname:
            db_attr = [(attrname, obj.attr(attrname))]
            try:
                ndb_attr = [(attrname, object.__getattribute__(obj.ndb, attrname))]
            except Exception:
                ndb_attr = None
        else:
            if self.player_mode:
                db_attr = [(attr.key, attr.value) for attr in PlayerAttribute.objects.filter(db_obj=obj)]
            else:
                db_attr = [(attr.key, attr.value) for attr in ObjAttribute.objects.filter(db_obj=obj)]
            try:
                ndb_attr = [(aname, avalue) for aname, avalue in obj.ndb.__dict__.items()]
            except Exception:
                ndb_attr = None
        string = ""
        if db_attr and db_attr[0]:
            #self.caller.msg(db_attr)
            string += headers["persistent"]
            for attr, value in db_attr:
                if crop and isinstance(value, basestring):
                    value = utils.crop(value.__str__())
                string += "\n %s = %s" % (attr, value)
        if ndb_attr and ndb_attr[0]:
            string += headers["nonpersistent"]
            for attr, value in ndb_attr:
                if crop and isinstance(value, basestring):
                    value = utils.crop(value.__str__())
                string += "\n %s = %s" % (attr, value)
        return string

    def format_output(self, obj, avail_cmdset, raw=False):
        """
        Helper function that creates a nice report about an object.

        returns a string.
        """

        headers = {"name":"\n{wName/key{n: {c%s{n (%s)",
                   "aliases":"\n{wAliases{n: %s",
                   "player":"\n{wPlayer{n: {c%s{n",
                   "playerperms":"\n{wPlayer Perms{n: %s",
                   "typeclass":"\n{wTypeclass{n: %s (%s)",
                   "location":"\n{wLocation{n: %s (#%s)",
                   "destination":"\n{wDestination{n: %s (#%s)",
                   "perms":"\n{wPermissions{n: %s",
                   "locks":"\n{wLocks{n:",
                   "cmdset":"\n{wCurrent Cmdset(s){n:\n %s",
                   "cmdset_avail":"\n{wCommands available to %s (all cmdsets + exits and external cmds){n:\n %s",
                   "scripts":"\n{wScripts{n:\n %s",
                   "exits":"\n{wExits{n: ",
                   "characters":"\n{wCharacters{n: ",
                   "contents":"\n{wContents{n: "}
        headers_noansi = {"name":"\nName/key: %s (%s)",
                   "aliases":"\nAliases: %s",
                   "player":"\nPlayer: %s",
                   "playerperms":"\nPlayer Perms: %s",
                   "typeclass":"\nTypeclass: %s%s",
                   "location":"\nLocation: %s (#%s)",
                   "destination":"\nDestination: %s (#%s)",
                   "perms":"\nPermissions: %s",
                   "locks":"\nLocks:",
                   "cmdset":"\nCurrent Cmdset(s):\n %s",
                   "cmdset_avail":"\nCommands available to %s (all cmdsets + exits and external cmds):\n %s",
                   "scripts":"\nScripts:\n %s",
                   "exits":"\nExits: ",
                   "characters":"\nCharacters: ",
                   "contents":"\nContents: "}
        if raw:
            headers = headers_noansi

        if hasattr(obj, "has_player") and obj.has_player:
            string = headers["name"] % (obj.name, obj.dbref)
        else:
            string = headers["name"] % (obj.name, obj.dbref)
        if hasattr(obj, "aliases") and obj.aliases:
            string += headers["aliases"] % (", ".join(obj.aliases))
        if hasattr(obj, "has_player") and obj.has_player:
            string += headers["player"] % obj.player.name
            perms = obj.player.permissions
            if obj.player.is_superuser:
                perms = ["<Superuser>"]
            elif not perms:
                perms = ["<None>"]
            string += headers["playerperms"] % (", ".join(perms))
        string += headers["typeclass"] % (obj.typeclass.typename, obj.typeclass_path)

        if hasattr(obj, "location") and obj.location:
            string += headers["location"] % (obj.location, obj.location.id)
        if hasattr(obj, "destination") and obj.destination:
            string += headers["destination"]  % (obj.destination, obj.destination.id)
        perms = obj.permissions
        if perms:
            string += headers["perms"] % (", ".join(perms))
        locks = str(obj.locks)
        if locks:
            string += headers["locks"] + utils.fill("; ".join([lock for lock in locks.split(';')]), indent=6)

        if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "Empty"):
            # list the current cmdsets
            #all_cmdsets = obj.cmdset.all() + (hasattr(obj, "player") and obj.player.cmdset.all() or [])
            all_cmdsets = obj.cmdset.all() + (obj.player and obj.player.cmdset.all() or [])
            all_cmdsets.sort(key=lambda x:x.priority, reverse=True)
            string += headers["cmdset"] % ("\n ".join("%s (prio %s)" % (cmdset.path, cmdset.priority) for cmdset in all_cmdsets))
            #cmdsetstr = "\n".join([utils.fill(cmdset, indent=2) for cmdset in str(obj.cmdset).split("\n")])

            # list the actually available commands
            avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])

            cmdsetstr = utils.fill(", ".join(avail_cmdset), indent=2)
            string += headers["cmdset_avail"] % (obj.key, cmdsetstr)

        if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
            string += headers["scripts"] % obj.scripts
        # add the attributes
        string += self.format_attributes(obj, raw=raw)
        # add the contents
        exits = []
        pobjs = []
        things = []
        if hasattr(obj, "contents"):
            for content in obj.contents:
                if content.destination:
                    exits.append(content)
                elif content.player:
                    pobjs.append(content)
                else:
                    things.append(content)
            if exits:
                string += headers["exits"] + ", ".join([exit.name for exit in exits])
            if pobjs:
                string += headers["characters"] + ", ".join(["{c%s{n" % pobj.name for pobj in pobjs])
            if things:
                string += headers["contents"] + ", ".join([cont.name for cont in obj.contents
                                                           if cont not in exits and cont not in pobjs])
        #output info
        return "-"*78 + '\n' + string.strip() + "\n" + '-'*78

    def func(self):
        "Process command"
        caller = self.caller

        msgdata = None
        if "raw" in self.switches:
            msgdata = {"raw":True}

        def get_cmdset_callback(cmdset):
            """
            We make use of the cmdhandeler.get_and_merge_cmdsets below. This
            is an asynchronous function, returning a Twisted deferred.
            So in order to properly use this we need use this callback;
            it is called with the result of get_and_merge_cmdsets, whenever
            that function finishes. Taking the resulting cmdset, we continue
            to format and output the result.
            """
            string = self.format_output(obj, cmdset, raw=msgdata)
            caller.msg(string.strip(), data=msgdata)

        if not self.args:
            # If no arguments are provided, examine the invoker's location.
            obj = caller.location
            if not obj.access(caller, 'examine'):
            #If we don't have special info access, just look at the object instead.
                caller.execute_cmd('look %s' % obj.name)
                return
            # using callback for printing result whenever function returns.
            get_and_merge_cmdsets(obj).addCallback(get_cmdset_callback)
            return

        # we have given a specific target object
        for objdef in self.lhs_objattr:

            obj_name = objdef['name']
            obj_attrs = objdef['attrs']

            self.player_mode = "player" in self.switches or obj_name.startswith('*')
            obj = caller.search(obj_name, player=self.player_mode)

            if not obj:
                continue

            if not obj.access(caller, 'examine'):
                #If we don't have special info access, just look at the object instead.
                caller.execute_cmd('look %s' % obj_name)
                continue
            if obj_attrs:
                for attrname in obj_attrs:
                    # we are only interested in specific attributes
                    caller.msg(self.format_attributes(obj, attrname, crop=False, raw=msgdata))
            else:
                # using callback to print results whenever function returns.
                get_and_merge_cmdsets(obj).addCallback(get_cmdset_callback)





